package org.enzy.morphose.mapping;

import factory.ConstructorFactory;
import org.enzy.morphose.mapping.exception.AmbiguousConstructorException;
import org.enzy.morphose.mapping.annotation.ValueFrom;
import org.enzy.morphose.mapping.annotation.MapsFrom;

import java.lang.annotation.Annotation;
import java.lang.reflect.Constructor;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

public class ConstructorBasedBeanMapper implements BeanMapper {

    public <T> T map(Object source, Class<T> T) {

        Constructor constructor = getMappableConstructor(source.getClass(), T);
        List<String> mappings = buildConstructorInvocationMap(constructor);

        return callConstructor(T, mappings, source);

    }

    private List<String> buildConstructorInvocationMap(Constructor constructor) {
        List<String> mappings = new ArrayList<String>();

        Annotation[][] annotations = constructor.getParameterAnnotations();
        for (int position=0;position<annotations.length;position++) {
            String methodName = findMappingReference(annotations, position);
            mappings.add(methodName);
        }

        return mappings;
    }

    private String findMappingReference(Annotation[][] annotations, int position) {
        for (Annotation annotation : annotations[position]) {
            if (annotation.annotationType().equals(ValueFrom.class)) {
                ValueFrom ref = (ValueFrom)annotation;
                return ref.value();
            }
        }
        return null;
    }

    private Constructor getMappableConstructor(Class sourceClass, Class destinationClass) {

        Constructor[] constructors = destinationClass.getConstructors();

        for (Constructor candidate : constructors) {

            if (candidate.isAnnotationPresent(MapsFrom.class)) {
                MapsFrom mappingDirective = (MapsFrom) candidate.getAnnotation(MapsFrom.class);
                for (Class source : mappingDirective.value()) {
                    if (sourceClass.equals(source)) {
                        return candidate;
                    }
                }
            }
            
        }

        throw new AmbiguousConstructorException(destinationClass);
    }

    private <T> T callConstructor(Class<T> destination, List<String> mappings, Object source) {

        ConstructorFactory<T> constructorFactory = new ConstructorFactory<T>();

        Constructor constructor = getMappableConstructor(source.getClass(), destination);
        return constructorFactory.instantiate(constructor, mappings, source);

    }

}
